#include <assert.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "CPU.h"
#include "base/Simulator.h"
#include "base/padding.h"
#include "base/simapi.h"

/*
  The linker script will insert padding between program globals and simulator
  globals. But, since we want inSimulation to be accessible from both, we
  need to make sure no other simulator globals are in the same cache line. So,
  we pad.
*/
PADDING(before_inSimulation);
bool inSimulation;    // both real and simulation mode can access this
PADDING(after_inSimulation);

void Sim_DisableShutdownAsserts () {
    ASSERT(!inSimulation);
    global_sim->cfg.shutdown_asserts = 0;
}

void Sim_Print (char const* fmt, ...) {
    if (!inSimulation) {
        va_list ap;
        va_start(ap,fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
        return;
    }
    Sim_Print_fmt(fmt);
    int argc = 0;
    va_list ap;
    va_start(ap, fmt);
    int i;
    for (i = 0; (fmt[i] != '\0') && (argc < 8); i++) {
        char f = fmt[i];
        if (f == '%') {
            while (true) {
                f = fmt[++i];
                if (f == '\0') {
                    Sim_Print1("End of format string reached trying to parse \"%s\".\n",
                               (int)fmt);
                    exit(1);
                }
                // skip precision and padding numbers
                if ('0' <= f && f <= '9') {
                    continue;
                }
                break;
            }
            switch (f) {
            case 's': {          /* string */
                char* s = va_arg(ap, char *);
                Sim_Print_argv((int)s, argc++);
                break;
            }
            case 'x':
            case 'd':
            case 'u': {          /* int */
                int d = va_arg(ap, int);
                Sim_Print_argv(d, argc++);
                break;
            }
            case 'p': {          /* pointer */
                void* p = va_arg(ap, void*);
                Sim_Print_argv((uint)p, argc++);
                break;
            }
            case 'f': {          /* double */
                double f = va_arg(ap, double);
                long long  ll = double2longlong(f);
                int a1        = ll >> 32;
                int a2        = ll &  0xFFFFFFFFULL;
                Sim_Print_argv(a1, argc++);
                Sim_Print_argv(a2, argc++);
                break;
            }
            case 'c': {          /* char */
                /* need a cast here since va_arg only
                   takes fully promoted types */
                char c = (char) va_arg(ap, int);
                Sim_Print_argv(c, argc++);
                break;
            }
            case '%': {          /* percent character */
                break;
            }
            default: {
                // format character was not recognized
                Sim_Print2("Unrecognized format character %c in format string \"%s\".\n",
                           (int)f, (int)fmt);
                exit(1);
            }
            }
        }
    }
    va_end(ap);
    Sim_Print_argc(argc);
}

static pthread_barrier_t sim_parallelfork_barrier;
static void *(*sim_parallelfork_run)(void*);
static void * sim_parallelfork_arg;

static void* sim_parallelfork_start_routine (void* arg)
{
    pthread_barrier_wait(&sim_parallelfork_barrier);
    return sim_parallelfork_run(sim_parallelfork_arg);
}

void Sim_ParallelFork (void *(*run)(void*), void* arg) {
    sim_parallelfork_run = run;
    sim_parallelfork_arg = arg;

    int thread_count = Sim_GetNumCpus();

    pthread_barrier_init(&sim_parallelfork_barrier, NULL, thread_count);

    pthread_t* threads = (pthread_t*)calloc(thread_count, sizeof(pthread_t));
    assert(threads != NULL);

    for (int i = 1 ; i < thread_count; i++) {
        int createResult = pthread_create(&threads[i], 
                                          NULL,
                                          sim_parallelfork_start_routine,
                                          NULL);
        assert(createResult == 0);
    }

    sim_parallelfork_start_routine(NULL);

    for (int i = 1 ; i < thread_count; i++) {
        int joinResult = pthread_join(threads[i], 
                                      NULL);
        assert(joinResult == 0);
    }

    pthread_barrier_destroy(&sim_parallelfork_barrier);

}
